package com.server;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * 服务器未来只有一个
 * 启动服务器之后就不能停掉了
 * 如果来了一个浏览器访问，需要启动一个服务器线程去处理当前浏览器的请求和响应
 * 服务器等待下一个浏览器
 */
public class Handler extends Thread {


    //因为这里启用多线程进行接收请求，run方法是无法传递参数的
    private Socket socket;

    public Handler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        this.receiveRequest(socket);
    }

    //为了管理每个controller对象是单例的
    //服务器内写一个管理对象的容器，
    private Map<String, HttpServlet> contrllerMap = new HashMap<>();


//这个方法就不需要了
//    private void start() {
//        System.out.println("== myTomcat start===");
//        //1、启动服务器：创建一个Socket接收请求
//        ServerSocket serverSocket = null;
//        try {
//            serverSocket = new ServerSocket(9999);
//            //2、等待客户端的连接（浏览器、其他发起请求的终端）
//            socket = serverSocket.accept();//阻塞效果，一直等待
//            System.out.println("有一个客户端连接过来了");
//            //接收请求进行相应的处理
//        } catch (IOException e) {
//            throw new RuntimeException(e);
//        }
//    }

    //3、流读取：读取浏览器发送过来的请求信息 ===》content?key=value
    private void receiveRequest(Socket socket) {
        try {
            //创建一个用来读取信息的输入流
            InputStream inputStream = socket.getInputStream();
            //将字节流转为成字符流（可以用来读取中文字符）这里io中的适配器模式，用InputStreamReader作为适配器，将字节流转为字符流
            //io中两个重要的适配器（Adapter）:InputStreamReader和 OutputStreamWriter
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            //将字符输入流转为字符缓冲输入流（提供一个读取一行的方法）
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            //读取浏览器发送过来的一行请求信息
            String content = bufferedReader.readLine();
            System.out.println("浏览器发送过来的一行请求信息:" + content);
            System.out.println("开始解析请求");
            parseContent(content);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //4、解析请求
    private void parseContent(String content) {
        //如果是浏览器传进来的，参数将会是这样：GET /index?username=hsc&pass=17 HTTP/1.1
        if (content.contains("HTTP/1.1")) {
            //如果包含协议说明是浏览器发送过来的请求
            String[] browerContent = content.split(" ");
            if (browerContent.length > 0) {
                // /index?username=hsc&pass=17这一部分内容
                // 因为真正的浏览器中带着  / 需要将截取掉 ====> index?username=hsc&pass=17
                content = browerContent[1].substring(1);
            }
        }
        //否则则是我们自己写的客户端的请求：
        // 传入进来的参数： content?key=value&key=value
        //获取content值
        String requestName;
        //获取key=value 键值对 Map存储
        Map<String, String> paramenterMap = null; //懒加载 如果后面带有在参数再进行创建
        //查找？开始的位置 ---content?key=value
        int index = content.indexOf("?");
        if (index != -1) { //说明是有参数的
            //截取content的内容，也就是请求访问路径
            requestName = content.substring(0, index);
            paramenterMap = new HashMap<>();
            //获取问号后面的所有的键值对key=value&key=value
            String allKeyAndValue = content.substring(index + 1);
            //key=value 根据&符号进行分隔 key=value&key=value
            String[] keyAndValues = allKeyAndValue.split("&");
            for (String keyAndValue : keyAndValues) {
                String[] KV = keyAndValue.split("=");
                //添加key 和value
                paramenterMap.put(KV[0], KV[1]);
            }
        } else {
            requestName = content;
        }
        System.out.println("解析到的请求名称：" + requestName);
        System.out.println("解析参数的信息：" + paramenterMap);
        //这里我们传递的这个两个参数可以使用HttpServletRequest对象进行存储
        HttpServletRequest request = new HttpServletRequest(requestName, paramenterMap);
        HttpServletResponse response = new HttpServletResponse();
        this.findServlet(request, response);
    }

    //5、用请求名字，找寻资源(文件/操作)
    private void findServlet(HttpServletRequest request, HttpServletResponse response) {
        //第一种获取文件流的方式
//        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("web.properties");
        try {
            //1、获取request对象中请求名
            String requestName = request.getRequestName();
            //2、根据请求名称找到对应的Controller对象，Controller都必须实现HttpServlet 中规定的方法
            HttpServlet servlet = contrllerMap.get(requestName);
            //如果为null
            if (servlet == null) {
                //找到对应的类
                //根据请求名称获取对应的类名称
                String className = MyServerReader.getValue(requestName);
                //4、通过类的名字反射加载类
                Class clazz = Class.forName(className);
                //5、通过clazz创建对象
                Constructor constructor = clazz.getConstructor();
                //向上转型
                servlet = (HttpServlet) constructor.newInstance();
                contrllerMap.put(requestName, servlet);
            }
            //执行servlet
            Class clazz = servlet.getClass();
            //6、通过clazz找寻类中的那个需要执行的方法，如果方法是携带参数的，需要将参数进行传递
            Method method = clazz.getMethod("service", HttpServletRequest.class, HttpServletResponse.class);
            //7、让方法执行
            method.invoke(servlet, request, response);
            //当调用万方法之后，response被填充满信息了
            //填充完信息之后，响应回浏览器
            this.responseToBrowser(response);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 6、资源执行完结果，将结果响应回去给浏览器
     *
     * @param response
     */
    private void responseToBrowser(HttpServletResponse response) {
        String responseContent = response.getResponseContent();
        try {
            //获取同个socket 输出流
            OutputStream outputStream = socket.getOutputStream();
            //转为字符输出流
            PrintWriter writer = new PrintWriter(outputStream);
            //真正响应回浏览器的位置
            writer.println(responseContent);
            writer.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}
